require 'classes'
require 'debugging'

----------------

class("Scheduler", Object)

function Scheduler:_construct(...)
    self.threadList = {}
    
    -- set up a global scheduler
    if (globalScheduler == nil) then
    	globalScheduler = self
    end
    
    self:superClass():_construct(...)
end

function Scheduler:addTask(taskObject)
    list = self.threadList
    list[#list+1] = taskObject
    taskObject._thread = coroutine.create(taskObject.main)  
end

function Scheduler:removeTask(taskObject)
	table.remove(self.threadList, taskObject)
end

function Scheduler:restartThread(taskObject)
	-- recreate the thread (in case taskObject.main changed)
	taskObject._thread = coroutine.create(taskObject.main)
end

function Scheduler:updateThreads()
    local iter = 0
    for i,v in ipairs(self.threadList) do
        iter = iter + 1
        if (coroutine.status(v._thread) ~= "dead") then
            success, result = coroutine.resume(v._thread, v)
            if (success == false) then
                error(result)
            end
        end
    end
end

function Scheduler:hasLivingThreads()
    -- check our list of tasks; if we have at least one that is ready, return true
    for i,v in ipairs(self.threadList) do
        if (coroutine.status(v._thread) ~= "dead") then
            return true
        end
    end
    
    -- all dead, or we had no threads to begin with
    return false
end

function Scheduler:cleanDeadThreads()
    -- remove all tasklets that have a dead thread
    for i,v in ipairs(self.threadList) do
        if (coroutine.status(v._thread) == "dead") then
            table.remove(self.threadList, i)
        end
    end
end

function Scheduler:clearTasks()
    self.threadList = {}
end

function Scheduler:start()
    self.shouldStop = false
    self:cleanDeadThreads()
    
    while(self.shouldStop ~= true and self:hasLivingThreads()) do -- it's the night of the living thread
        self:cleanDeadThreads()
        self:updateThreads()
    end
end

function Scheduler:stop()
	self.shouldStop = true
end

class("Channel", Object)

function Channel:_construct(...)
    self.message = nil
    self:superClass():_construct(...)
end

function Channel:send(message)
    -- first block until the channel is ok to send on
    while (self.message ~= nil) do
        coroutine.yield()
    end
    
	self.message = message
	
	-- now block until the message has been received before returning
	while (self.message ~= nil) do
		coroutine.yield()
	end
end

function Channel:receive()
	-- block until there's something to receive
	while (self.message == nil) do
		coroutine.yield()
	end
	
	local v = self.message
	self.message = nil
	return v
end

-- Make source tidier by aliasing coroutine.yield
yield = coroutine.yield

-----------

class ("Tasklet", Object)

function Tasklet:_construct(...)
    self.channel = Channel:create()
    self:superClass():_construct(...)
    --[[if (globalScheduler ~= nil) then
    	globalScheduler:addTask(self)
    end]]
end

-- public function to handle one-to-one communication
function Tasklet:message(message)
    self.channel:send(message)
end

function Tasklet:main()
	-- generic loop that discards all messages
	while (self.channel:receive()) do end
end

-- sleep for a certain number of seconds
function Tasklet:sleep(duration)
    -- todo: os.time is only integer resolution, replace with a better function
    
    local timeToWake = timer.microtime() + duration
    
    while (timer.microtime() < timeToWake) do
        --print(self, timeToWake - timer.microtime())
        coroutine.yield()
    end
end
